package ua.kharkov.khpi.tarasenko.bach.service;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import ua.kharkov.khpi.tarasenko.bach.analysis.prefixspan.Item;
import ua.kharkov.khpi.tarasenko.bach.analysis.prefixspan.Node;
import ua.kharkov.khpi.tarasenko.bach.analysis.tnr.RedBlackTree;
import ua.kharkov.khpi.tarasenko.bach.analysis.tnr.RuleG;
import ua.kharkov.khpi.tarasenko.bach.core.EShopException;
import ua.kharkov.khpi.tarasenko.bach.core.db.EShopDataStoreException;
import ua.kharkov.khpi.tarasenko.bach.core.logging.CustomerSession;
import ua.kharkov.khpi.tarasenko.bach.core.logging.TransactionType;
import ua.kharkov.khpi.tarasenko.bach.core.utils.RuleUtils;
import ua.kharkov.khpi.tarasenko.bach.dao.core.DAOFactory;
import ua.kharkov.khpi.tarasenko.bach.dao.core.DAOFactory.DAO_TYPE;
import ua.kharkov.khpi.tarasenko.bach.dao.core.assocrules.AssocRulesDAO;
import ua.kharkov.khpi.tarasenko.bach.dao.core.tree.TreeDAO;
import ua.kharkov.khpi.tarasenko.bach.dao.core.usersession.UserSessionDAO;
import ua.kharkov.khpi.tarasenko.bach.dao.mysql.assocrules.AssocRulesDAOImpl;
import ua.kharkov.khpi.tarasenko.bach.dao.mysql.tree.TreeDAOImpl;
import ua.kharkov.khpi.tarasenko.bach.web.ui_beans.RecommendationBean;

public class AnalysisService {

	static {
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private Connection conn;

	public void saveTreeToDB(Node rootNode) {

		// root node does not have item in it and parent
		int parentId = 1;

		Node rootStubNode = new Node();

		Item rootItemStub = Item.getItem("0");
		rootItemStub.setTransactionType(TransactionType.VIEW);

		rootStubNode.setItem(rootItemStub);
		rootStubNode.setSupport(0);

		
		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		TreeDAO treeDAO = new TreeDAOImpl(conn);
		int nodeId = treeDAO.saveNode(rootStubNode);
		treeDAO.savePath(nodeId, parentId);
		
		
		saveTreeToDB(rootNode, parentId);
	}

	private void saveTreeToDB(Node rootNode, int parentId) {

		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		TreeDAO treeDAO = new TreeDAOImpl(conn);
		for (Node node : rootNode.getChildrenNodes()) {
			int nodeId = treeDAO.saveNode(node);
			treeDAO.savePath(nodeId, parentId);
			if (node.hasChildren()) {
				saveTreeToDB(node, nodeId);
			}

		}

		try {
			conn.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public Node getTreeFromDB() {
		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		TreeDAO treeDAO = new TreeDAOImpl(conn);

		Map<Integer, Node> nodes = treeDAO.getAllNodes();
		Set<Integer> keys = nodes.keySet();
		for (Integer id : keys) {
			Node node = nodes.get(id);
			System.out.println(id + "\t" + node.getItemS() + "\t"
					+ node.getItem().getTransactionType().ordinal() + "\t"
					+ node.getSupport());
		}

		// Get root node
		Node rootNode = nodes.get(1);

		// Get root node's direct childs
		rootNode.setChildren(getChildren(nodes, 1, treeDAO));

		return rootNode;
	}

	public void deleteTreeFromDB() {
		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		TreeDAO treeDAO = new TreeDAOImpl(conn);
		treeDAO.deleteTree();
	}

	private Map<String, Node> getChildren(Map<Integer, Node> nodes,
			int parentId, TreeDAO treeDAO) {
		Map<String, Node> children = new HashMap<String, Node>();
		List<Integer> childIds = treeDAO.getDirectChildNodesId(parentId);
		for (Integer childId : childIds) {
			Node child = nodes.get(childId);
			child.setChildren(getChildren(nodes, childId, treeDAO));
			children.put(child.getItemS(), child);
		}

		return children;
	}

	public boolean isTreeInDb() throws EShopDataStoreException {

		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "root");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		TreeDAO treeDAO = new TreeDAOImpl(conn);

		return treeDAO.isTreeInDb();
	}

	public void saveRules(RedBlackTree<RuleG> rules)
			throws EShopDataStoreException {

		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		AssocRulesDAO rulesDAO = new AssocRulesDAOImpl(conn);
		Iterator<RuleG> it = rules.iterator();
		while (it.hasNext()) {
			rulesDAO.saveRule(it.next());
		}
	}

	/*
	 * ==========================================================================
	 * =======
	 * 
	 * MOVE TO COMMAND SHOW_RECOMMENDATIONS_SETINGS
	 * 
	 * ==========================================================================
	 * =======
	 */

	public int getNumberOfRulesInDb() {
		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		AssocRulesDAO rulesDAO = new AssocRulesDAOImpl(conn);
		return rulesDAO.getNumberOfRules();
	}

	public void deleteRules() {
		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		AssocRulesDAO rulesDAO = new AssocRulesDAOImpl(conn);
		rulesDAO.deleteAllRules();
	}

	/*
	 * ==========================================================================
	 * =======
	 * 
	 * MOVE TO RECOMMENDATION SERVICE!
	 * 
	 * ==========================================================================
	 * =======
	 */

	private RecommendationBean getNavRecommendations(
			List<Integer> accessSequence) throws SQLException, EShopException {

		Connection conn = null;

			conn = conn = DriverManager.getConnection(
					"jdbc:mysql://localhost/eshop", "root", "root");
			UserSessionDAO userSessionDAO = DAOFactory.getDAOFactory(
					DAO_TYPE.MYSQL).getUserSessionDAO(conn);
			List<CustomerSession> session = userSessionDAO.getAllSessions();
			if (session.size() > 0) {

			}


		return null;
	}

	/**
	 * 
	 * 
	 * @param itemsInCart
	 *            - ids should be sorted in ASC order!
	 * @param num
	 * @return
	 */
	public Integer[] getDontForgetRecommendations(Integer[] itemsInCart, int num) {

		Connection conn = null;
		try {
			conn = DriverManager.getConnection("jdbc:mysql://localhost/eshop",
					"root", "");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// Get possible combinations of items from the cart in case there is no
		// rules(or number of items in Consequent is less than "num"
		List<Integer[]> itemSets = new ArrayList<Integer[]>();
		for (int i = 0; i < itemsInCart.length; i++) {
			Integer[] curr = new Integer[1];
			curr[0] = itemsInCart[i];
			Integer[] another = Arrays.copyOfRange(itemsInCart, i + 1,
					itemsInCart.length);
			itemSets.addAll(generateCandidates(curr, another));
		}

		// Sort list in order: first long then short combinations
		Collections.sort(itemSets, new Comparator<Integer[]>() {

			@Override
			public int compare(Integer[] o1, Integer[] o2) {
				return o2.length - o1.length;
			}
		});

		// Now we need to fetch "num" items which are not in "itemsInCart"
		// We start with long item sets

		// Get hashSet from Array for fast contains method
		Set<Integer> itemsInCartSet = new HashSet<Integer>();
		for (Integer id : itemsInCart) {
			itemsInCartSet.add(id);
		}

		Integer[] recs = new Integer[num];
		Set<Integer> foundItems = new HashSet<Integer>();
		int gotRecs = 0;
		AssocRulesDAO rulesDAO = new AssocRulesDAOImpl(conn);

		Iterator<Integer[]> it = itemSets.iterator();
		outer: while ((gotRecs < num) && (it.hasNext())) {

			Integer[] currItemSet = it.next();
			List<String> conseqs = rulesDAO
					.getConsequentsByAntecedent(RuleUtils
							.idsArrayToString(currItemSet));

			// For each consequent from db for current itemSet
			for (String consequent : conseqs) {
				Integer[] ids = RuleUtils.parseRuleIdsList(consequent);

				for (Integer id : ids) {
					if (!itemsInCartSet.contains(id)
							&& !foundItems.contains(id)) {
						recs[gotRecs] = id;
						foundItems.add(id);
						gotRecs++;

						if (gotRecs == num) {
							break outer;
						}
					}
				}
			}

		}

		return recs;
	}

	private static List<Integer[]> generateCandidates(Integer[] curr,
			Integer[] another) {
		List<Integer[]> candidates = new ArrayList<Integer[]>();
		candidates.add(curr);
		if (another.length != 0) {

			for (int i = 0; i < another.length; i++) {
				Integer[] newCand = Arrays.copyOf(curr, curr.length + 1);
				newCand[newCand.length - 1] = another[i];

				Integer[] newAnother = Arrays.copyOfRange(another, i + 1,
						another.length);
				candidates.addAll(generateCandidates(newCand, newAnother));
			}
		}
		return candidates;
	}
}
